package top.mydb.hejianwei.dm.pageCache;

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import top.mydb.hejianwei.common.Error;
import top.mydb.hejianwei.dm.page.Page;
import top.mydb.hejianwei.dm.page.PageImpl;
import top.mydb.hejianwei.utils.Panic;

public class PageCacheImpl implements PageCache {

    private static final int MEM_MIN_LIM = 10; // 缓存最小页数限制
    public static final String DB_SUFFIX = ".db";

    private RandomAccessFile file;
    private FileChannel fc;
    private Lock fileLock;

    // 缓存数据结构
    private Map<Integer, Page> pageCache; // 页号 -> 页面对象
    private Map<Integer, Integer> references; // 页号 -> 引用计数
    private Lock cacheLock;

    private AtomicInteger pageNumbers; // 记录当前文件有多少页
    private int maxResource; // 缓存最大页数

    /**
     * 构造函数，用于打开一个已有的.db文件
     */
    public PageCacheImpl(RandomAccessFile file, int maxResource) {
        this.file = file;
        this.fileLock = new ReentrantLock();
        this.cacheLock = new ReentrantLock();
        this.maxResource = maxResource;
        this.pageCache = new HashMap<>();
        this.references = new HashMap<>();

        try {
            this.fc = file.getChannel();
            long length = file.length();
            // 计算初始页面数
            this.pageNumbers = new AtomicInteger((int)length / PAGE_SIZE);
        } catch (Exception e) {
            Panic.panic(e);
        }
    }

    @Override
    public int newPage(byte[] initData) {
        // 1. 获取新页的页号
        int pgno = pageNumbers.incrementAndGet();
        
        // 2. 根据页号和初始数据，构造一个新Page对象
        Page pg = new PageImpl(pgno, initData, this);

        // 3. 将新页加入缓存
        cacheLock.lock();
        try {
            // 先尝试淘汰一页，为新页面腾出空间
            evict();
            pageCache.put(pgno, pg);
            references.put(pgno, 1);
        } finally {
            cacheLock.unlock();
        }
        return pgno;
    }

    @Override
    public Page getPage(int pgno) throws Exception {
        while(true) {
            cacheLock.lock();
            try {
                // 1. 尝试从缓存获取
                Page pg = pageCache.get(pgno);
                if (pg != null) {
                    references.put(pgno, references.get(pgno) + 1);
                    return pg;
                }

                // 2. 缓存中没有，检查缓存是否已满
                if (maxResource > 0 && pageCache.size() == maxResource) {
                    throw Error.CacheFullException;
                }

                // 3. 从磁盘加载
                byte[] buf = new byte[PAGE_SIZE];
                fileLock.lock();
                try {
                    long offset = (long)(pgno - 1) * PAGE_SIZE;
                    ByteBuffer byteBuffer = ByteBuffer.wrap(buf);
                    fc.position(offset);
                    fc.read(byteBuffer);
                } catch (Exception e) {
                    Panic.panic(e);
                } finally {
                    fileLock.unlock();
                }

                // 4. 将加载的页面放入缓存
                Page newPage = new PageImpl(pgno, buf, this);
                pageCache.put(pgno, newPage);
                references.put(pgno, 1);
                return newPage;

            } finally {
                cacheLock.unlock();
            }
        }
    }

    @Override
    public void release(Page page) {
        cacheLock.lock();
        try {
            int pgno = page.getPageNumber();
            int refCount = references.get(pgno) - 1;
            if (refCount == 0) {
                references.remove(pgno);
            } else {
                references.put(pgno, refCount);
            }
        } finally {
            cacheLock.unlock();
        }
    }

    @Override
    public synchronized void close() {
        cacheLock.lock();
        try {
            for (Page page : pageCache.values()) {
                if (page.isDirty()) {
                    flush(page);
                }
            }
            pageCache.clear();
            references.clear();

            try {
                fc.close();
                file.close();
            } catch (Exception e) {
                Panic.panic(e);
            }
        } finally {
            cacheLock.unlock();
        }
    }

    @Override
    public void truncateByBgno(int maxPgno) {
        long size = (long)maxPgno * PAGE_SIZE;
        try {
            file.setLength(size);
        } catch (Exception e) {
            Panic.panic(e);
        }
        pageNumbers.set(maxPgno);
    }
    
    @Override
    public int getPageNumber() {
        return pageNumbers.get();
    }


    /**
     * 淘汰一页。这是一个简单的随机淘汰策略。
     * 找到一个引用计数为0的页，将其从缓存移除。
     * 如果是脏页，需要先刷回磁盘。
     */
    private void evict() {
        if (pageCache.size() < maxResource) {
            return; // 缓存未满，无需淘汰
        }
        // 随机选择一个页号进行淘汰
        int victim = -1;
        for(Integer pgno : pageCache.keySet()) {
            if(!references.containsKey(pgno)) {
                victim = pgno;
                break;
            }
        }
        if(victim == -1) {
             Panic.panic(Error.CacheFullException);
        }
        
        Page pg = pageCache.get(victim);
        if (pg.isDirty()) {
            flush(pg);
        }
        pageCache.remove(victim);
    }

    /**
     * 将指定页面刷回磁盘
     * @param pg 需要刷回的页面
     */
    private void flush(Page pg) {
        int pgno = pg.getPageNumber();
        long offset = (long)(pgno - 1) * PAGE_SIZE;

        fileLock.lock();
        try {
            ByteBuffer buf = ByteBuffer.wrap(pg.getData());
            fc.position(offset);
            fc.write(buf);
            fc.force(false);
        } catch (Exception e) {
            Panic.panic(e);
        } finally {
            fileLock.unlock();
        }
        // 刷盘后，页面的脏状态可以取消
        pg.setDirty(false);
    }
}